/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.util;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.github.wnameless.json.flattener.JsonFlattener;
import com.iwindplus.base.domain.constant.CommonConstant;
import com.iwindplus.base.domain.constant.CommonConstant.ApiSignConstant;
import com.iwindplus.base.domain.constant.CommonConstant.NumberConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import com.iwindplus.base.util.domain.dto.ApiSignGenerateDTO;
import com.iwindplus.base.util.domain.dto.ApiSignVerifyDTO;
import java.time.Duration;
import java.util.Map;
import java.util.TreeMap;
import lombok.extern.slf4j.Slf4j;

/**
 * API签名工具类.
 *
 * @author zengdegui
 * @since 2021/1/11
 */
@Slf4j
public class ApiSignUtil {

    private ApiSignUtil() {
        throw new IllegalStateException(CommonConstant.UTILITY_CLASS);
    }

    /**
     * 生成签名.
     * </p>
     * 第一步，加密串=accessKey + timestamp + nonce + method + path + params（按照字典序排序，拼接成字符串，例如："key1=value1&key2=value2"）
     * </p>
     * 第二步，签名=MD5(HmacSHA256(加密串))
     *
     * @param entity 对象
     * @return String
     */
    public static String generateSign(ApiSignGenerateDTO entity) {
        ApiSignUtil.checkApiSignGenerateParam(entity);

        Map<String, Object> entityMap = new TreeMap<>();
        entityMap.put(ApiSignConstant.X_ACCESS_KEY, entity.getAccessKey());
        entityMap.put(ApiSignConstant.X_TIMESTAMP, entity.getTimestamp());
        entityMap.put(ApiSignConstant.X_NONCE, entity.getNonce());
        entityMap.put(ApiSignConstant.X_METHOD, entity.getMethod());
        entityMap.put(ApiSignConstant.X_PATH, entity.getPath());

        final Map<String, Object> params = entity.getParams();
        if (ObjectUtil.isNotEmpty(params)) {
            final String jsonStr = JacksonUtil.toJsonStr(MapUtil.removeNullValue(params));
            final Map<String, Object> flatten = JsonFlattener.flattenAsMap(jsonStr);
            entityMap.putAll(MapUtil.removeNullValue(flatten));
        }

        final String data = HttpsUtil.concatMap(entityMap);

        HMac hmac = SecureUtil.hmac(HmacAlgorithm.HmacSHA256, entity.getSecretKey());
        final String result = hmac.digestHex(data);
        return SecureUtil.md5(result);
    }

    /**
     * 验证签名.
     *
     * @param entity 对象
     * @return boolean
     */
    public static boolean verifySign(ApiSignVerifyDTO entity) {
        ApiSignUtil.checkSignVerifyParam(entity);

        String sign = ApiSignUtil.generateSign(entity);
        return CharSequenceUtil.equals(entity.getSign(), sign);
    }

    private static void checkApiSignGenerateParam(ApiSignGenerateDTO entity) {
        // 密钥
        final String secretKey = entity.getSecretKey();
        if (CharSequenceUtil.isBlank(secretKey)) {
            throw new BizException(BizCodeEnum.SECRET_KEY_NOT_EXIST);
        }

        // 访问key
        final String accessKey = entity.getAccessKey();
        if (CharSequenceUtil.isBlank(accessKey)) {
            throw new BizException(BizCodeEnum.ACCESS_KEY_NOT_EXIST);
        }

        // 时间戳
        String timestamp = entity.getTimestamp();
        if (CharSequenceUtil.isBlank(timestamp)) {
            throw new BizException(BizCodeEnum.TIMESTAMP_NOT_EXIST);
        }
        if (!CharSequenceUtil.isNumeric(timestamp)) {
            throw new BizException(BizCodeEnum.ONLY_SUPPORT_NUMBER);
        }

        // 随机数
        String nonce = entity.getNonce();
        if (CharSequenceUtil.isBlank(nonce) || nonce.length() < NumberConstant.NUMBER_TEN) {
            throw new BizException(BizCodeEnum.INVALID_NONCE);
        }

        // 请求路径
        final String path = entity.getPath();
        if (CharSequenceUtil.isBlank(path)) {
            throw new BizException(BizCodeEnum.REQUEST_PATH_NOT_EXIST);
        }

        // 请求方式
        final String method = entity.getMethod();
        if (CharSequenceUtil.isBlank(method)) {
            throw new BizException(BizCodeEnum.REQUEST_METHOD_NOT_EXIST);
        }
    }

    private static void checkSignVerifyParam(ApiSignVerifyDTO entity) {
        // 签名
        final String sign = entity.getSign();
        if (CharSequenceUtil.isBlank(sign)) {
            throw new BizException(BizCodeEnum.SIGN_NOT_EXIST);
        }

        // 超时时间
        final Duration timeout = entity.getTimeout();
        if (ObjectUtil.isEmpty(timeout)) {
            throw new BizException(BizCodeEnum.SIGN_TIMEOUT_NOT_EXIST);
        }

        final String timestamp = entity.getTimestamp();
        if (ApiSignUtil.isExpired(Long.valueOf(timestamp), timeout)) {
            throw new BizException(BizCodeEnum.SIGN_EXPIRED, new Object[]{entity.getTimeout().toSeconds()});
        }
    }

    private static boolean isExpired(long timestamp, Duration timeout) {
        long now = System.currentTimeMillis();
        long delta = Math.abs(now - timestamp);
        return delta > timeout.toMillis();
    }

}